package com.hcss.common.util.excel.util;

import com.hcss.common.util.excel.cell.CellUtil;
import com.hcss.common.util.excel.rull.ExcelRullResult;
import com.hcss.common.util.excel.rull.RullData;
import com.hcss.common.util.excel.rull.RullResult;
import com.hcss.common.util.excel.rull.RullType;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.io.File;
import java.io.InputStream;
import java.util.*;

/**
 * Excel 读取器
 * @Description:
 * @Author:momo
 * @Since:2018年12月21日
 * @Version:1.1.0
 */
@Getter
@Setter
public class ExcelReader {
	
	/** 工作簿 */
	private Workbook workbook;
	/** Excel中对应的Sheet */
	private Sheet sheet;
	/** 是否忽略空行 */
	private boolean ignoreEmptyRow = true;
	/** 标题别名 */
	private Map<String, String> headerAlias = new HashMap<>();
	/** 规则表 */
	private Map<String, RullType> rullMap = new HashMap<>();
	
	
	/**
	 * 构造
	 * 
	 * @param excelFilePath Excel文件路径，绝对路径或相对于ClassPath路径
	 */
	public ExcelReader(String excelFilePath) {
		this(ExcelFileUtil.touch(excelFilePath), 0, null);
	} 
		
	/**
	 * 构造
	 * 
	 * @param excelFilePath Excel文件路径，绝对路径或相对于ClassPath路径
	 * @param sheetIndex sheet序号，0表示第一个sheet
	 */
	public ExcelReader(String excelFilePath, int sheetIndex) {
		this(ExcelFileUtil.touch(excelFilePath), sheetIndex, null);
	} 

	/**
	 * 构造
	 * 
	 * @param bookFile Excel文件
	 * @param sheetIndex sheet序号，0表示第一个sheet
	 * @param password 是否有密码， 没有的话传 null
	 */
	public ExcelReader(File bookFile, int sheetIndex, String password) {
		Workbook book = WorkbookUtil.createBook(bookFile, password);
		Sheet sheet = book.getSheetAt(sheetIndex);
		this.sheet = sheet;
		this.workbook = sheet.getWorkbook();
	}
	
	/**
	 * 构造
	 * 
	 * @param is Excel文件
	 * @param sheetIndex sheet序号，0表示第一个sheet
	 * @param password 是否有密码， 没有的话传 null
	 */
	public ExcelReader(InputStream is, int sheetIndex, String password) {
		Workbook book = WorkbookUtil.createBook(is, password);
		Sheet sheet = book.getSheetAt(sheetIndex);
		this.sheet = sheet;
		this.workbook = sheet.getWorkbook();
	}
	
	/**
	 * 读取工作簿中指定的Sheet的所有行列数据
	 * @return 行的集合，一行使用List表示
	 */
	public List<List<Object>> read() {
		return read(0);
	}

	/**
	 * 读取工作簿中指定的Sheet
	 * 
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @return 行的集合，一行使用List表示
	 */
	public List<List<Object>> read(int startRowIndex) {
		return read(startRowIndex, Integer.MAX_VALUE);
	}
	
	/**
	 * 读取工作簿中指定的Sheet
	 * 
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @param endRowIndex 结束行（包含，从0开始计数）
	 * @return 行的集合，一行使用List表示
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<List<Object>> read(int startRowIndex, int endRowIndex) {
		List<List<Object>> resultList = new ArrayList<>();
		startRowIndex = Math.max(startRowIndex, this.sheet.getFirstRowNum());// 读取起始行（包含）
		endRowIndex = Math.min(endRowIndex, this.sheet.getLastRowNum());// 读取结束行（包含）
		
		boolean isFirstLine = true;
		List rowList;
		for (int i = startRowIndex; i <= endRowIndex; i++) {
			rowList = readRow(i);
			if ((rowList != null && rowList.size() != 0) || false == ignoreEmptyRow) {
				if (null == rowList) {
					rowList = new ArrayList<>(0);
				}
				if (isFirstLine) {
					isFirstLine = false;
					if (this.headerAlias != null && this.headerAlias.size() != 0) {
						rowList = aliasHeader(rowList);
					}
				}
				resultList.add(rowList);
			}
		}
		return resultList;
	}
	
// -------------------------------------------------------------------------------------- 读取Excel为Bean------------------------------
	
	/**
	 * 读取Excel为Bean的列表，读取所有行，默认第一行做为标题，数据从第二行开始
	 * 
	 * @param <T> Bean类型
	 * @param beanType 每行对应Bean的类型
	 * @return Map的列表
	 */
	public <T> List<T> readAll(Class<T> beanType) {
		return read(0, 1, Integer.MAX_VALUE, beanType);
	}

	/**
	 * 读取Excel为Bean的列表
	 * 
	 * @param <T> Bean类型
	 * @param headerRowIndex 标题所在行，如果标题行在读取的内容行中间，这行做为数据将忽略，，从0开始计数
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @param beanType 每行对应Bean的类型
	 * @return Map的列表
	 */
	public <T> List<T> read(int headerRowIndex, int startRowIndex, Class<T> beanType) {
		return read(headerRowIndex, startRowIndex, Integer.MAX_VALUE, beanType);
	}

	/**
	 * 读取Excel为Bean的列表
	 * 
	 * @param <T> Bean类型
	 * @param headerRowIndex 标题所在行，如果标题行在读取的内容行中间，这行做为数据将忽略，，从0开始计数
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @param endRowIndex 读取结束行（包含，从0开始计数）
	 * @param beanType 每行对应Bean的类型
	 * @return Map的列表
	 */
	@SuppressWarnings("unchecked")
	public <T> List<T> read(int headerRowIndex, int startRowIndex, int endRowIndex, Class<T> beanType) {
		final List<Map<String, Object>> mapList = read(headerRowIndex, startRowIndex, endRowIndex);
		if (Map.class.isAssignableFrom(beanType)) {
			return (List<T>) mapList;
		}

		final List<T> beanList = new ArrayList<>(mapList.size());
		for (Map<String, Object> map : mapList) {
			beanList.add(ExcelBeanUtil.transMap2Bean2(map, beanType));
		}
		return beanList;
	}
	
	/**
	 * 读取Excel为Map的列表<br>
	 * Map表示一行，标题为key，单元格内容为value
	 * 
	 * @param headerRowIndex 标题所在行，如果标题行在读取的内容行中间，这行做为数据将忽略
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @param endRowIndex 读取结束行（包含，从0开始计数）
	 * @return Map的列表
	 */
	public List<Map<String, Object>> read(int headerRowIndex, int startRowIndex, int endRowIndex) {
		// 边界判断
		final int firstRowNum = sheet.getFirstRowNum();
		final int lastRowNum = sheet.getLastRowNum();
		if (headerRowIndex < firstRowNum) {
			throw new IndexOutOfBoundsException("headerRowIndex lower than first rowIndex");
		} else if (headerRowIndex > lastRowNum) {
			throw new IndexOutOfBoundsException("HeaderRowIndex is greater than lastRowIndex");
		}
		startRowIndex = Math.max(startRowIndex, firstRowNum);// 读取起始行（包含）
		endRowIndex = Math.min(endRowIndex, lastRowNum);// 读取结束行（包含）

		// 读取header
		List<Object> headerList = readRow(sheet.getRow(headerRowIndex));

		final List<Map<String, Object>> result = new ArrayList<>(endRowIndex - startRowIndex + 1);
		List<Object> rowList;
		for (int i = startRowIndex; i <= endRowIndex; i++) {
			if (i != headerRowIndex) {
				// 跳过标题行
				rowList = readRow(sheet.getRow(i));
				if ((rowList != null && rowList.size() != 0) || false == ignoreEmptyRow) {
					if (null == rowList) {
						rowList = new ArrayList<>(0);
					}
					result.add(toMap(aliasHeader(headerList), rowList, true));
				}
			}
		}
		return result;
	}
	
	public static <K, V> Map<K, V> toMap(Iterable<K> keys, Iterable<V> values, boolean isOrder) {
		return toMap(null == keys ? null : keys.iterator(), null == values ? null : values.iterator(), isOrder); 
	}
	
	/**
	 * 将键列表和值列表转换为Map<br>
	 * 以键为准，值与键位置需对应。如果键元素数多于值元素，多余部分值用null代替。<br>
	 * 如果值多于键，忽略多余的值。
	 * 
	 * @param <K> 键类型 
	 * @param <V> 值类型 
	 * @param keys 键列表 
	 * @param values 值列表 
	 * @param isOrder 是否有序 
	 * @return 标题内容Map 
	 */
	public static <K, V> Map<K, V> toMap(Iterator<K> keys, Iterator<V> values, boolean isOrder) { 
		final Map<K, V> resultMap = isOrder ? new LinkedHashMap<K, V>() : new HashMap<K, V>(); 
		if (keys != null && keys.hasNext()) { 
			while (keys.hasNext()) { 
				resultMap.put(keys.next(), (null != values && values.hasNext()) ? values.next() : null); 
			} 
		}
		return resultMap; 
	}
	
	
	
	public List<Object> readRow(int rowIndex) {
		Row row = this.sheet.getRow(rowIndex);
		if (null == row) {
			return new ArrayList<>(0);
		}
		final short length = row.getLastCellNum();
		if (length < 0) {
			return new ArrayList<>(0);
		}
		final List<Object> cellValues = new ArrayList<>( length );
		Object cellValue;
		boolean isAllNull = true;
		for (short i = 0; i < length; i++) {
			cellValue = CellUtil.getCellValue(row.getCell(i));
			if(cellValue instanceof String) {
				isAllNull &= StringUtils.isEmpty((String)cellValue); // 用于判断是否这一行都为空
			}else {
				isAllNull &= (cellValue == null);
			}
			cellValues.add(cellValue);
		}

		if (isAllNull) {
			// 如果每个元素都为空，则定义为空行
			return new ArrayList<>(0);
		}
		return cellValues;
	}
	
	/**
	 * 读取一行
	 * 
	 * @param row 行
	 * @return 单元格值列表
	 */
	public static List<Object> readRow(Row row) {
		if (null == row) {
			return new ArrayList<>(0);
		}
		final short length = row.getLastCellNum();
		if (length < 0) {
			return new ArrayList<>(0);
		}
		final List<Object> cellValues = new ArrayList<>( length );
		Object cellValue;
		boolean isAllNull = true;
		for (short i = 0; i < length; i++) {
			cellValue = CellUtil.getCellValue(row.getCell(i));
			if(cellValue instanceof String) {
				isAllNull &= StringUtils.isEmpty((String)cellValue); // 用于判断是否这一行都为空
			}else {
				isAllNull &= (cellValue == null);
			}
			cellValues.add(cellValue);
		}

		if (isAllNull) {
			return new ArrayList<>(0);
		}
		return cellValues;
	}
	
	/**
	 * 转换标题别名，如果没有别名则使用原标题
	 * 
	 * @param headerList 原标题列表
	 * @return 转换别名列表
	 */
	private List<String> aliasHeader(List<Object> headerList) {
		final ArrayList<String> result = new ArrayList<>();
		if (headerList == null || headerList.size() == 0) {
			return result;
		}

		String header;
		String alias = null;
		for (Object headerObj : headerList) {
			if (null != headerObj) {
				header = headerObj.toString();
				if(this.headerAlias.get(header) != null) {
					alias = this.headerAlias.get(header);
				}else {
					alias = header;
				}
			}
			result.add(alias);
		}
		return result;
	}
	
	// --------------------------------- 带有校验类型的read ----------------------------------
	
	public <T> ExcelRullResult<?> readAllWithRull(Class<T> beanType) throws Exception {
		return readWithRull(0, 1, Integer.MAX_VALUE, beanType); // 这一点一定要注意，错误时和正确是泛型是不一致的。这一点其实这么设计是有问题的。不过时间原因先这么写了。
	}
	
	/**  
	 * 读取Excel为Bean的列表 
	 * 
	 * @param <T> Bean类型
	 * @param headerRowIndex 标题所在行，如果标题行在读取的内容行中间，这行做为数据将忽略，，从0开始计数
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @param endRowIndex 读取结束行（包含，从0开始计数）
	 * @param beanType 每行对应Bean的类型
	 * @return Map的列表
	 */
	public <T> ExcelRullResult<?> readWithRull(int headerRowIndex, int startRowIndex, int endRowIndex, Class<T> beanType) throws Exception {
		final ExcelRullResult<Map<String, Object>> excelRull = readWithRull(headerRowIndex, startRowIndex, endRowIndex);
		if(!excelRull.getResult()) {
			return excelRull;
		}
		List<Map<String, Object>> mapList = excelRull.getList();
		final List<T> beanList = new ArrayList<>(mapList.size());
		for (Map<String, Object> map : mapList) {
			beanList.add(ExcelBeanUtil.transMap2Bean2(map, beanType));
		}
		return ExcelRullResult.getSuccResult(beanList);
	}
	
	/**
	 * 读取Excel为Map的列表<br>
	 * Map表示一行，标题为key，单元格内容为value
	 * 
	 * @param headerRowIndex 标题所在行，如果标题行在读取的内容行中间，这行做为数据将忽略
	 * @param startRowIndex 起始行（包含，从0开始计数）
	 * @param endRowIndex 读取结束行（包含，从0开始计数）
	 * @return Map的列表
	 */
	public ExcelRullResult<Map<String, Object>> readWithRull(int headerRowIndex, int startRowIndex, int endRowIndex) throws Exception {
		// 边界判断
		final int firstRowNum = sheet.getFirstRowNum();
		final int lastRowNum = sheet.getLastRowNum();
		if (headerRowIndex < firstRowNum) {
			throw new IndexOutOfBoundsException("headerRowIndex lower than first rowIndex");
		} else if (headerRowIndex > lastRowNum) {
			throw new IndexOutOfBoundsException("HeaderRowIndex is greater than lastRowIndex");
		}
		startRowIndex = Math.max(startRowIndex, firstRowNum);// 读取起始行（包含）
		endRowIndex = Math.min(endRowIndex, lastRowNum);// 读取结束行（包含）

		// 读取header
		List<Object> headerList = readRow(sheet.getRow(headerRowIndex));

		final List<Map<String, Object>> result = new ArrayList<>(endRowIndex - startRowIndex + 1);
		List<Object> rowList;
		for (int i = startRowIndex; i <= endRowIndex; i++) {
			if (i != headerRowIndex) {
				// 跳过标题行
				rowList = readRow(sheet.getRow(i)); // 按顺序读取的
				if ((rowList != null && rowList.size() != 0) || false == ignoreEmptyRow) {
					if (null == rowList) {
						rowList = new ArrayList<>(0);
					}
					RullResult rr = toMapWithRull(aliasHeader(headerList), rowList, i, true);
					if(!rr.getResult()) {
						return ExcelRullResult.getFailResult(rr.getMessage());
					}else {
						result.add(rr.getRetMap());
					}
				}
			}
		}
		return ExcelRullResult.getSuccResult(result);
	}
	
	
	
	private RullResult toMapWithRull(List<String> aliasHeader, List<Object> rowList, int rowNum, boolean isOrder) throws Exception {
		final Map<String, Object> resultMap = isOrder ? new LinkedHashMap<String, Object>() : new HashMap<String, Object>();
		Iterator<String> keys = aliasHeader.iterator();
		Iterator<Object> values = rowList.iterator();
		if (keys != null && keys.hasNext()) { 
			int colNum = 0;
			while (keys.hasNext()) { 
				String key = keys.next();
				Object value = (null != values && values.hasNext()) ? values.next() : null;
				if(null != rullMap.get(key)) {
					RullType rull = rullMap.get(key);
					RullResult rr = rull.rull(new RullData(rowNum, colNum, value));
					if(!rr.getResult()) {
						return rr;
					}else {  
						resultMap.put(key, value);
					}
				}else {
					resultMap.put(key, value); 
				}
				colNum ++;
			} 
		}
		return RullResult.getSuccResult(resultMap);
		       
	}	

	public static void main(String[] args) {
		ExcelReader reader = new ExcelReader("D:\\alias.xlsx");
		List<List<Object>> readAll = reader.read();
		
		System.out.println(readAll.toString());
		
	}

	
	
}

    